ECS RunTaskで実行されるSpring Batchアプリケーションにおいて、特定のエラー条件時のみリトライさせてみる

ECS RunTaskで実行されるSpring Batchアプリケーションにおいて、特定のエラー条件時のみリトライさせてみる

ECS Runtask を定期実行する際、Step Functions を利用することで特定条件でリトライすることが可能です。
この際、Step Functions の再試行オプションを活用できます。

スクリーンショット 2025-01-10 19.32.26.png

ただ、アプリケーションの終了コード等を使ってより柔軟なリトライを行いたいケースもあるかもしれません。
その場合は Lambda を活用したループ処理を実行する必要があり、aws-samples リポジトリでサンプル実装が公開されています。

https://github.com/aws-samples/retryable-ecs-run-task-step-functions/tree/main

今回は上記実装を Spring Batch の場合に置き換えつつ、試してみます。

構築内容

構成としては下図のような形にしました。
Step Functions 関連リソース以外に、Spring Batch のメタデータ格納用の Aurora PostgreSQL クラスターと定期実行用の EventBridge Scheduler を追加しています。

Untitled(30).png

ワークフローとしては下記になります。
こちらは aws-samples のものと変わりません。
Lambda を用いて柔軟なリトライ判定を行いつつ、一定回数分繰り返します。

スクリーンショット 2025-01-10 19.51.11.png

バッチ処理の内容としては、S3 からファイルをダウンロードするだけのシンプルなものにしています。
Step Functions から Runtask を呼び出す際に ContainerOverrides で CMD を指定しており、ここで実行ジョブ名を指定しています。
今回実行するジョブは 1 つだけなので意味が無いですが、このようにすることでコンテナやタスク定義をジョブ数分用意する必要がなくなります。

スクリーンショット 2025-01-10 19.53.08.png

S3 に指定されたファイルが無い時は ABANDONED(6)で終了させ、それ以外の時は FAILED(5)で終了させます。

@Component("FileDownloadTasklet")
@StepScope
@Slf4j
public class FileDownloadTasklet implements Tasklet {

  @Override
  public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
    StepExecution stepExecution = chunkContext.getStepContext().getStepExecution();
    S3Client s3Client = S3Client.builder()
        .region(Region.AP_NORTHEAST_1)
        .build();
    String bucketName = System.getenv("BUCKET_NAME");
    String fileKey = System.getenv("FILE_KEY");
    try {
      GetObjectRequest getObjectRequest = GetObjectRequest.builder()
          .bucket(bucketName)
          .key(fileKey)
          .build();
      s3Client.getObject(getObjectRequest);
    } catch (S3Exception e) {
       log.error(e.awsErrorDetails().errorMessage());
      if (e.awsErrorDetails().errorCode().equals("NoSuchKey")) {
        log.error("NoSuchKey");
        stepExecution.setStatus(BatchStatus.ABANDONED);
      } else {
        stepExecution.setStatus(BatchStatus.FAILED);
      }
    }

    return RepeatStatus.FINISHED;
  }

}

Lambda の中で終了コードを確認して 6 の場合のみリトライを行います。
また、CannotPullContainerError と ResourceInitializationError の時もリトライを行います。

def is_retryable(cause):
    # Retryable errors ECS returns
    # https://docs.aws.amazon.com/AmazonECS/latest/userguide/stopped-task-error-codes.html
    if cause['StoppedReason'].startswith("CannotPullContainerError"):
        return True
    if cause['StoppedReason'].startswith("ResourceInitializationError"):
        return True

    # Retryable errors your application returns
    contianer = cause['Containers'][0]
    if contianer.get('ExitCode', 0) in [6]:
        return True
    return False

CDK ベースで実装しており、コードは下記リポジトリに格納しているので、もしご興味があればご確認下さい。

https://github.com/masutaro99/step-functions-spring-batch

試してみる

では、試してみます。
せっかく Event Bridge Scheduler を作ったのですが、検証するには面倒なので手動で Step Functions を起動します。
まず、ECR に指定したイメージが無い状態で起動してみます。
CannotPullContainerError 扱いになるので、3 回リトライを行いつつ、最終的に失敗扱いになります。

スクリーンショット 2025-01-10 20.15.06.png

スクリーンショット 2025-01-10 20.17.30.png

今度はイメージはあるものの、S3 に指定したファイルが無いパターンで試してみます。
この場合も、終了コードがが指定した値 (6) になるので 3 回ループして失敗扱いとなります。

スクリーンショット 2025-01-10 20.30.08.png

次に S3 に途中でファイルを格納するパターンで試してみます。
バッチ処理に必要なファイルが遅れて格納されたイメージです。
リトライを経て、最終的には成功扱いになりました!

スクリーンショット 2025-01-12 1.56.20.png

最初から S3 にファイルがあれば、もちろん 1 回目で成功します。

スクリーンショット 2025-01-12 1.59.32.png

最後に S3 自体が無いパターンで試してみます。
特定条件に当てはまらないので、リトライもせず失敗になります。

スクリーンショット 2025-01-12 2.02.12.png

まとめ

Step Functions 経由で ECS Runtask リトライ処理を試してみました。
Lambda を利用すれば ExitCode ごとに柔軟な処理を行う事が可能です。
このようなループ処理は直近追加された「変数」機能で楽に実装できそうなので、そちらも試してみようと思ってます。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.